﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace WinHooksNET
{
    public class MouseGesturesModule
    {
        #region Members
        public event EventHandler<Events.MouseGestureEventArgs> MouseGestureChecked;

        private List<Structs.Gesture> _gesturesList = new List<Structs.Gesture>();
        private List<Structs.MouseCoords> _mousePointsList = new List<Structs.MouseCoords>();
        private List<Events.MouseGestureEventArgs> _eventsHistory = new List<Events.MouseGestureEventArgs>();

        private MouseHook _mouseHooks = new MouseHook();
        
        private bool _isGestureMaking = false;
        private bool _handleMouseGesture = true;

        private Enums.MouseButtons _gestureActionButton = Enums.MouseButtons.LeftMouseButton;
        private Enums.FilterMode _filterMode = Enums.FilterMode.BooleanFilters;
        
        private int _gestureTolerance = 10;
        private int _historyLimit = 100;

        private Func<bool> _customFilterFunc;

        /// <summary>
        /// Determine tolerance for gestures. If value is highter, a gesture must be done more precisely
        /// </summary>
        public int GestureTolerance
        {
            get { return _gestureTolerance; }
            set { _gestureTolerance = value; }
        }

        /// <summary>
        /// List of called events. Limit of items can be set in HistoryLimit var
        /// </summary>
        public List<Events.MouseGestureEventArgs> EventsHistory
        {
            get { return _eventsHistory; }
            set { _eventsHistory = value; }
        }

        /// <summary>
        /// Determine Filter Mode. BooleanFilter for bool variables or CustomFunction for CustomFilterFunc var.
        /// </summary>
        public Enums.FilterMode FilterMode
        {
            get { return _filterMode; }
            set { _filterMode = value; }
        }

        /// <summary>
        /// Custom function used by filter. FilterMode must be set to a CustomFunction
        /// </summary>
        public Func<bool> CustomFilterFunc
        {
            get { return _customFilterFunc; }
            set { _customFilterFunc = value; }
        }

        /// <summary>
        /// Limit of items in called event history. If 0, history can be empty
        /// </summary>
        public int HistoryLimit
        {
            get { return _historyLimit; }
            set 
            { 
                _historyLimit = value;
                if (EventsHistory.Count >= HistoryLimit)
                {
                    EventsHistory.RemoveRange(HistoryLimit, EventsHistory.Count - HistoryLimit);
                }
            }
        }

        /// <summary>
        /// Determine whether the library is to intercept mouse gestures
        /// </summary>
        public bool HandleMouseGesture
        {
            get { return _handleMouseGesture; }
            set { _handleMouseGesture = value; }
        }

        /// <summary>
        /// Determine which mouse button you will be responsible for the initiation and termination of a mouse gesture.
        /// </summary>
        public Enums.MouseButtons GestureActionButton
        {
            get { return _gestureActionButton; }
            set { _gestureActionButton = value; }
        }

        /// <summary>
        /// Determine whether user is acutally making gesture
        /// </summary>
        public bool IsGestureMaking
        {
            get { return _isGestureMaking; }
            set { _isGestureMaking = value; }
        }
        #endregion

        public MouseGesturesModule()
        {
            _mouseHooks.OnMouseAction += OnMouseAction;
            CustomFilterFunc = new Func<bool>(() => { return true; });
        }

        #region Functions
        /// <summary>
        /// Register new gesture
        /// </summary>
        /// <param name="name">Name of the gesture</param>
        /// <param name="checkPoints">Points, that are used in analyse gesture</param>
        public void RegisterGesture(String name, bool[,] checkPoints)
        {
            //If gesture exist, throw exception
            if(_gesturesList.Exists(p => p.Name == name))
                throw new Exceptions.NameExistException("Gesture name is already registred");

            //Get size of points array
            int xLenght = checkPoints.GetLength(0);
            int yLenght = checkPoints.GetLength(1);

            //Init converted points array
            int[,] convertedCheckPoints = new int[xLenght, yLenght];

            //Convert bool points to int
            for (int x = 0; x < xLenght; x++)
            {
                for (int y = 0; y < yLenght; y++)
                {
                    convertedCheckPoints[x, y] = Convert.ToInt32(checkPoints[x, y]);
                }
            }

            //Init new gesture
            Structs.Gesture gesture = new Structs.Gesture()
            {
                Name = name,
                CheckPoinstArray = convertedCheckPoints
            };
            
            //Add gesture to list
            _gesturesList.Add(gesture);
        }

        /// <summary>
        /// Register new gesture
        /// </summary>
        /// <param name="name">Name of the gesture</param>
        /// <param name="checkPoints">Points, that are used in analyse gesture</param>
        public void RegisterGesture(String name, int[,] checkPoints)
        {
            //If gesture exist, throw exception
            if (_gesturesList.Exists(p => p.Name == name))
                throw new Exceptions.NameExistException("Gesture name is already registred");

            //Init new gesture
            Structs.Gesture gesture = new Structs.Gesture();
            gesture.Name = name;

            //Get size of points array
            int xLenght = checkPoints.GetLength(0);
            int yLenght = checkPoints.GetLength(1);

            //Set converted array to gesture
            gesture.CheckPoinstArray = checkPoints;

            //Add gesture to list
            _gesturesList.Add(gesture);
        }

        /// <summary>
        /// Unregister gesture
        /// </summary>
        /// <param name="name">Name of the gesture</param>
        public void UnregisterGesture(String name)
        {
            //If gesture exist, remove it
            if(_gesturesList.Exists(p => p.Name == name))
            {
                _gesturesList.RemoveAll(p => p.Name == name);
            }
        }

        private void OnMouseAction(object sender, Events.MouseHookEventArgs e)
        {
            if ((e.Action == Enums.MouseAction.LeftButtonPressed && GestureActionButton == Enums.MouseButtons.LeftMouseButton) ||
                (e.Action == Enums.MouseAction.RightButtonPressed && GestureActionButton == Enums.MouseButtons.RightMouseButton))
            {
                //If left button is pressed, start work
                _isGestureMaking = true;
            }
            else if ((e.Action == Enums.MouseAction.LeftButtonReleased && GestureActionButton == Enums.MouseButtons.LeftMouseButton) ||
                      e.Action == Enums.MouseAction.RightButtonReleased && GestureActionButton == Enums.MouseButtons.RightMouseButton)
            {
                //If left button is released, stop work
                _isGestureMaking = false;

                if (_mousePointsList.Count > 2)
                {
                    //Get gesture that was doing
                    Structs.Gesture gesture = TestGesture();
                    if (gesture.Name != "-1" && gesture.CheckPoinstArray != null)
                    {
                        Events.MouseGestureEventArgs eventArgs =  new Events.MouseGestureEventArgs()
                        {
                            Name = gesture.Name,
                            GestureArray = gesture.CheckPoinstArray,
                            Time = DateTime.Now.Ticks
                        };

                        AddEventToHistory(eventArgs);

                        //Call MouseGestureChecked event
                        MouseGestureChecked(this, eventArgs);
                    }
                }

                _mousePointsList.Clear();
            }
            else if(e.Action == Enums.MouseAction.MouseMove)
            {
                //If mouse is moved, add point to coords list
                if (_isGestureMaking)
                {
                    _mousePointsList.Add(e.Coords);
                }
            }
        }

        private Structs.Gesture TestGesture()
        {
            //Get min-max values from mouse coords list
            int gestureMaxX = _mousePointsList.Max(p => p.X);
            int gestureMinX = _mousePointsList.Min(p => p.X);
            int gestureMaxY = _mousePointsList.Max(p => p.Y);
            int gestureMinY = _mousePointsList.Min(p => p.Y);

            //Get area of gesture
            int gestureAreaX = gestureMaxX - gestureMinX;
            int gestureAreaY = gestureMaxY - gestureMinY;

            for (int i = 0; i < _gesturesList.Count; i++)
            {
                //Get size of Check Points array
                int xLenght = _gesturesList[i].CheckPoinstArray.GetLength(0);
                int yLenght = _gesturesList[i].CheckPoinstArray.GetLength(1);

                //Get size of single field
                float singleFieldX = (gestureAreaX / xLenght);
                float singleFieldY = (gestureAreaY / yLenght);

                int currentFieldIndex = -1;

                //Single field must be highter than 5
                if (singleFieldX > 5 && singleFieldY > 5)
                {
                    bool[,] fixedGestureArray = new bool[xLenght, yLenght];
                    for (int x = 0; x < _mousePointsList.Count; x++)
                    {
                        //Get index of fields, where mouse coord is
                        int fieldX = (int)Math.Floor((double)(_mousePointsList[x].X - gestureMinX) / singleFieldX);
                        int fieldY = (int)Math.Floor((double)(_mousePointsList[x].Y - gestureMinY) / singleFieldY);

                        //Indexes can't be highter than size of array
                        if (fieldX >= xLenght)
                            fieldX = xLenght - 1;

                        if (fieldY >= yLenght)
                            fieldY = yLenght - 1;

                        if (_gesturesList[i].CheckPoinstArray[fieldY, fieldX] < currentFieldIndex &&
                            _gesturesList[i].CheckPoinstArray[fieldY, fieldX] != 0)
                        {
                            break;
                        }
                        else
                        {
                            currentFieldIndex = _gesturesList[i].CheckPoinstArray[fieldY, fieldX];
                        }

                        fixedGestureArray[fieldY, fieldX] = true;
                    }

                    int goodCheckPoints = 0;
                    int badCheckPoints = 0;

                    for (int x = 0; x < xLenght; x++)
                    {
                        for (int y = 0; y < yLenght; y++)
                        {
                            if (fixedGestureArray[x, y] && _gesturesList[i].CheckPoinstArray[x, y] != 0)
                            {
                                goodCheckPoints++;
                            }
                            else if ((fixedGestureArray[x, y] && _gesturesList[i].CheckPoinstArray[x, y] == 0) ||
                                (!fixedGestureArray[x, y] && _gesturesList[i].CheckPoinstArray[x, y] != 0))
                            {
                                badCheckPoints++;
                            }
                        }
                    }

                    int totalfields = xLenght * yLenght;

                    float badPercents = (badCheckPoints * 100) / totalfields;
                    if (badPercents <= _gestureTolerance)
                    {
                        return _gesturesList[i];
                    }
                }
            }

            return new Structs.Gesture()
            {
                Name = "-1",
                CheckPoinstArray = null
            };
        }

        private bool IsActionAllowed()
        {
            if (_filterMode == Enums.FilterMode.BooleanFilters)
            {
                if (HandleMouseGesture)
                {
                    return true;
                }
            }
            else if (_filterMode == Enums.FilterMode.CustomFunction)
            {
                bool result = _customFilterFunc();
                return result;
            }

            return false;
        }

        private void AddEventToHistory(Events.MouseGestureEventArgs _eventArgs)
        {
            if (HistoryLimit != 0)
            {
                if (EventsHistory.Count >= HistoryLimit)
                {
                    if (EventsHistory.Count > 0)
                    {
                        EventsHistory.RemoveAt(EventsHistory.Count - 1);
                    }
                }
                EventsHistory.Insert(0, _eventArgs);
            }
        }
        #endregion
    }
}
